//
// Xiaodi Wu (xiaodiwu), Mac Wiltzer (maclain)
//
// This file includes the implementation of BufMgr class
//


#include <memory.h>
#include "buf.h"

//----------------------------------------
// Constructor of the class BufMgr
// Initialize the variables
// Set numbufs be bufs in the input
//----------------------------------------

BufMgr::BufMgr(const unsigned int bufs)
{
   // Initializations
  numBufs=bufs;
  clockHand=0;
  bufTable=new BufDesc[numBufs];
  bufPool=new Page[numBufs];
}

//----------------------------------------
// Deconstructor of the class BufMgr
// Flush all the dirty pages out 
// and deallocate all the memory
//----------------------------------------

BufMgr::~BufMgr() 
{
   // Flushes out all dirty pages
  unsigned int i;
  for(i=0;i<numBufs;i++)
    if (bufTable[i].valid && bufTable[i].dirty)
      bufTable[i].file->writePage(bufTable[i].pageNo,bufPool+i); //flush back this page
  // no need to stat here     
  // deallocate the buffer pool and BufDesc table
  delete[] bufTable;
  delete[] bufPool;
}


//------------------------------------------------------------------------
// allocBuf(frame) use clock algorithm to find an available 
// frame in the buffer pool and return that page through 
//  the parameter frame. Return OK if success. Otherwise return some error
//------------------------------------------------------------------------

const Status BufMgr::allocBuf(unsigned int & frame) 
{    
  unsigned int count=0,total=2*numBufs; 
   //if you go over all the pages twice without find an available page to evict, then all pages must be pinned
  bool found=false; unsigned int tframe;
  Status mystat;
 
  while (count<total)
    {
      count++; advanceClock();

      if (!bufTable[clockHand].valid) {found=true;tframe=clockHand;break;}// Ready to Use it
	//Otherwise
      if (bufTable[clockHand].refbit) {
        //Clear Refbit 
	bufTable[clockHand].refbit=false;
         continue;
      }
      //otherwise
      if (bufTable[clockHand].pinCnt>0) {  continue;} //pinned
      //otherwise
      found=true;
      if (bufTable[clockHand].dirty) {
	// flush the page into disk
         mystat=bufTable[clockHand].file->writePage(bufTable[clockHand].pageNo,bufPool+clockHand);
        if (mystat!=OK) return mystat; 
        bufStats.diskwrites++;
      }
      // delete from the buffer map 
        mystat=bufMap.remove(bufTable[clockHand].file, bufTable[clockHand].pageNo);
        if (mystat!=OK) return mystat;

      //otherwise
      tframe=clockHand;
      break;
    }
 
  if (found) {
    frame=tframe;
    bufTable[tframe].Clear(); //clear the state.
    // do not zero fill , lazily doing the thing
    // let the caller handle this
      return OK;
  }else return BUFFEREXCEEDED; 
}

	
//--------------------------------------------------------------------------
// readPage(file, pageNo, page) read in the page of file whose page number 
// is pageNo if that page is not in the buffer pool. Otherwise, set the refbit
// and pinCnt of the frame containing that page. Return a pointer page pointing 
// to that frame. Return OK on sucess. Otherwise return some error 
//---------------------------------------------------------------------------

const Status BufMgr::readPage(File* file, const int PageNo, Page*& page)
{
  //stat
  bufStats.accesses++;
  //
  if (!file) return BADFILEPTR;
  //check if page is in the buffer
  Status status;
  unsigned int frameRef;
  status = bufMap.lookup(file, PageNo, frameRef);
  if(status != OK) //not in buffer so allocate page
    {
       status = allocBuf(frameRef); //create bufferframe for page
      if(status != OK)
	return status;
         
      status = file->readPage(PageNo, bufPool+frameRef); //read page onto buffer
      if(status != OK)
	return status;
      //stat
      bufStats.diskreads++;
     
      bufTable[frameRef].Set(file, PageNo); //finally invoke Set     
      page=bufPool+frameRef; //set the Page pointer
 
      // add into map 
      status=bufMap.insert(file,PageNo, frameRef);
      if (status!=OK)
        return status;
    }
  else
    {
      //set refbit and increment pinCnt
      bufTable[frameRef].refbit = true;
      bufTable[frameRef].pinCnt++;
      //
      page = bufPool + frameRef;//return the Page pointer to the page desired
    }
  return OK;
}

//------------------------------------------------------------------------
// unPinPage(file, PageNo, dirty) decrease the pinCnt of the frame containing
// (file, PageNo). Set the frame dirtybit true if dirty is true. Return OK on 
// success and some error otherwise.
//------------------------------------------------------------------------

const Status BufMgr::unPinPage(File* file, const int PageNo, 
			       const bool dirty) 
{  
  if (!file) return BADFILEPTR;
   // get the frame
  Status mystat; unsigned int myframe;
  mystat=bufMap.lookup(file,PageNo, myframe);
  if (mystat!=OK) return mystat; //frame not found
  //
  if (!bufTable[myframe].valid) return BADBUFFER; //pointing to an invalid page
  if (bufTable[myframe].pinCnt==0) return PAGENOTPINNED; // pinCnt==0
  // 
  // then the real changes
  bufTable[myframe].pinCnt--;
  //
  if (dirty)  //set dirty bit
    bufTable[myframe].dirty=true;
  //
  return OK;
}

//-------------------------------------------------------------------------
// flushFile(file) flush out all the dirty pages of that file. If some page is 
// still pinned return an error. Return OK on success.
//-------------------------------------------------------------------------

const Status BufMgr::flushFile(File* file) 
{  if (!file) return BADFILEPTR;
   // scan the bufTable
  Status mystat;

  // first detects the error
  //since these pages are all unpinned and thus will be replaced by clock algorithm in the future and remove from the buffer Map
  // thus no delete from Map here!!!

  PageMapPtr myPageMap;

  mystat=bufMap.maplookup(file, myPageMap);
  if (mystat!=OK) // which means no pages belonging to that file
    return OK;

  // Test whether some page pinned
  PageMapItr PageMapIt;
  for (PageMapIt=myPageMap->begin();PageMapIt!=myPageMap->end();PageMapIt++)
    if (bufTable[(*PageMapIt).second].pinCnt>0) return PAGEPINNED;

  //Ok, all pages unpinned, then handle dirty page
  for (PageMapIt=myPageMap->begin();PageMapIt!=myPageMap->end();PageMapIt++)
    if (bufTable[(*PageMapIt).second].dirty)
      {
        mystat=file->writePage(bufTable[(*PageMapIt).second].pageNo, bufPool+(*PageMapIt).second);
        if (mystat!=OK) return mystat;
        bufStats.diskwrites++;
       }

   return OK;
}

//---------------------------------------------------------------------------
// disposePage(file, pageNo) dispose that page from the file. If that page is 
// in the buffer pool, clear the frame containing that page. Return OK on success
// and some error otherwise.
//---------------------------------------------------------------------------

const Status BufMgr::disposePage(File* file, const int pageNo)
{  
  if (!file) return BADFILEPTR;

  // BADPAGENO
  if (pageNo==0) return BADPAGENO;

  Status mystat; unsigned int myframe;

  //get that frame
  mystat=bufMap.lookup(file, pageNo, myframe);
  if (mystat==OK) { // this page exists
    if (bufTable[myframe].pinCnt>0) return PAGEPINNED; //currently pinned
    if (!bufTable[myframe].valid) return BADBUFFER; //pointing to an invalid page
    //clear this frame  
    bufTable[myframe].Clear();
    //remove from bufMap
    mystat=bufMap.remove(file,pageNo);
    if (mystat!=OK) return mystat;   
  }
  
  //disallocate
  mystat=file->disposePage(pageNo);
  if (mystat!=OK) return mystat;
  //stat
  bufStats.diskwrites++;

   return OK; 
}

//----------------------------------------------------------------------
// allocPage(file, pageNo, page) allocates one page for that file and 
// find one frame in the buufer pool to hold that page. Return the pointer
// to that frame by page. Return OK on success and some error otherwise.
//----------------------------------------------------------------------

const Status BufMgr::allocPage(File* file, int& pageNo, Page*& page) 
{  
  //stat
  bufStats.accesses++;
  //
  if (!file) return BADFILEPTR;
   // Allocate a new frame in the buffer pool
  unsigned int myframe;
  Status mystat;
  mystat=allocBuf(myframe);
  if (mystat!=OK) 
	  return mystat;

  // Allocate the new page on the new frame which was allocated earlier
  mystat = file->allocatePage(pageNo);
  if (mystat!=OK) 
	  return mystat;
  bufStats.diskwrites++;

  //Set page pointer to the page which myframe is referencing, then 
  //update the buffer map
  page = bufPool + myframe;
  mystat = bufMap.insert(file, pageNo, myframe);
  if (mystat!=OK) 
    return mystat;

  // zero fit this new page
  bufTable[myframe].Set(file, pageNo);
  bzero((char*)page, sizeof(Page));
  //
  return OK;
}

